package sample;

import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;
import sample.magic_square_sol.MSMain;
import sample.matrices.MagicSquare;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;

public class MagicSquareController {

    @FXML
    public static Label generationLabel;

    @FXML
    public static Label errorLabel;
    @FXML
    GridPane genErrGridPane;

    @FXML
    RadioButton yesRadioButton;

    @FXML
    RadioButton noRadioButton;

    @FXML
    Slider dimensionSlider;

    @FXML
    TextField dimensionTextField;

    @FXML
    TextField magicSumTextField;

    @FXML
    Button initialButton;

    @FXML
    ToggleGroup toggleGroup = new ToggleGroup();

    @FXML
    AnchorPane anchorPane;

    @FXML
    GridPane gridPane;

    @FXML
    Button returnButton;
    @FXML
    void handleReturn() throws Exception {
        Main.replaceSceneContent("initial_scene.fxml");
    }

    public static int generation = 0;
    public static int error = 0;

    MagicSquare magicSquare;

    public boolean hasConstrain = false;
    @FXML
    void handleYes(){
        yesRadioButton.setToggleGroup(toggleGroup);
        hasConstrain = true;
    }

    @FXML
    void handleNo(){
        noRadioButton.setToggleGroup(toggleGroup);
        hasConstrain = false;
    }

    @FXML
    void handleSliderDragDetected(){
        int d = (int)dimensionSlider.getValue();
        dimensionTextField.setText(String.valueOf(d));
        int magicSum = (1+d*d)*d/2;
        magicSumTextField.setText(String.valueOf(magicSum));
    }

    int state = 0; // 0 initial; 1 start; 2 stop
    public static int dimension = 20;
    public static int[][] data;
    public boolean[][] isfixed;
    public static Label[][] labels;
    public static int[] sumData;
    public static Label[] sumLables;

    boolean isOpen = false;
    @FXML
    void handleInitial() throws InterruptedException {
        if(state == 0){
            state = 1;
            //gridPane.getChildren().clear();
            initialButton.setText("START");
            //generation set
            generationLabel = new Label("  Generation: " + generation);
            generationLabel.setStyle("-fx-font-weight:bold;-fx-font-size:23px");
            genErrGridPane.add(generationLabel,0,0);
            //generationLabel.setText("  Generation: " + generation);

            //error set
            errorLabel = new Label("  Error: " + error);
            errorLabel.setStyle("-fx-font-weight:bold;-fx-font-size:23px");
            genErrGridPane.add(errorLabel,0,1);
            //errorLabel.textProperty().setValue("  Error: " + error);

            if(!isOpen){
                dimension = Integer.parseInt(dimensionTextField.getText());
            }
            else {
                dimensionTextField.setText(String.valueOf(dimension));
            }

            if(!isOpen){
                data = new int[dimension+1][dimension+1];
            }

            labels = new Label[dimension+1][dimension+1];
            if(!isOpen){
                isfixed = new boolean[dimension+1][dimension+1];
            }

            magicSquare = new MagicSquare(dimension+1);
            magicSquare.setData(data);

            //give values to matrix
            for(int i=1;i<=dimension;i++){
                for(int j=1;j<=dimension;j++){
                    if(!isOpen){
                        isfixed[i][j] = false;
                        data[i][j] = (i-1)*dimension+j;
                    }

                    labels[i][j] = new Label(""+data[i][j]);
                    labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:green");
                    if(isOpen){
                        if(isfixed[i][j]){
                            labels[i][j].setStyle("-fx-font-weight:bold;-fx-text-fill:olive");
                        }
                    }
                }
            }

            int offset = (20-dimension)/2;
            int oddRightOffset = offset+1;
            for(int i=1;i<=dimension;i++){
                for(int j=1;j<=dimension;j++){
                    gridPane.add(labels[i][j],j+offset,i+offset);
                }
            }

            int outSum = 4*dimension+4;
            sumData = new int[outSum];
            sumLables = new Label[outSum];

            //left upper 0
            int s0 = 0;
            for(int i=1;i<=dimension;i++){
                s0 += data[i][i];
            }
            sumData[0] = s0;
            sumData[1] = s0;
            sumLables[0] = new Label(""+sumData[0]);
            sumLables[1] = new Label(""+sumData[1]);
            gridPane.add(sumLables[0], offset, offset);
            if(dimension%2 == 0){
                gridPane.add(sumLables[1], 21-offset, 21-offset);
            }else {
                gridPane.add(sumLables[1], 21-oddRightOffset, 21-oddRightOffset);
            }


            //upper 2,4,...,2*dimension
            for(int i=2;i<=2*dimension;i+=2){
                int s = 0;
                for(int j=1;j<=dimension;j++){
                    s += data[i/2][j];
                }
                sumData[i] = s;
                sumData[i+1] = s;
                sumLables[i] = new Label(""+sumData[i]);
                sumLables[i+1] = new Label(""+sumData[i+1]);
                gridPane.add(sumLables[i], offset+i/2, offset);
                if(dimension%2 == 0){
                    gridPane.add(sumLables[i+1], offset+i/2, 21-offset);
                }
                else {
                    gridPane.add(sumLables[i+1], offset+i/2, 21-oddRightOffset);
                }
            }


            // right upper 2*dimension+2
            s0 = 0;
            for(int i=1;i<=dimension;i++){
                s0 += data[i][dimension+1-i];
            }
            sumData[2*dimension+2] = s0;
            sumData[2*dimension+3] = s0;
            sumLables[2*dimension+2] = new Label(""+sumData[2*dimension+2]);
            sumLables[2*dimension+3] = new Label(""+sumData[2*dimension+3]);
            if(dimension%2 == 0){
                gridPane.add(sumLables[2*dimension+2], 21-offset, offset);
                gridPane.add(sumLables[2*dimension+3], offset,21-offset);
            }
            else {
                gridPane.add(sumLables[2*dimension+2], 21-oddRightOffset, offset);
                gridPane.add(sumLables[2*dimension+3], offset,21-oddRightOffset);
            }


            //right 2*dimension+2+i
            for(int i=2; i<=2*dimension; i+=2){
                int s = 0;
                for(int j=1;j<=dimension;j++){
                    s += data[i/2][j];
                }
                sumData[2*dimension+2+i] = s;
                sumData[2*dimension+3+i] = s;
                sumLables[2*dimension+2+i] = new Label(""+sumData[2*dimension+2+i]);
                sumLables[2*dimension+3+i] = new Label(""+sumData[2*dimension+3+i]);
                if(dimension%2 == 0){
                    gridPane.add(sumLables[2*dimension+2+i], 21-offset, offset + i/2);
                }
                else {
                    gridPane.add(sumLables[2*dimension+2+i], 21-oddRightOffset, offset + i/2);
                }

                gridPane.add(sumLables[2*dimension+3+i], offset, offset + i/2);
            }

            for(int i=0;i<sumLables.length;i++){
                sumLables[i].setStyle("-fx-font-weight:bold;-fx-text-fill:purple");
            }

            AtomicInteger setValueCount = new AtomicInteger(1);

            if(hasConstrain){
                for(int i=1;i<=dimension;i++){
                    for(int j=1;j<=dimension;j++){
                        int finalI = i;
                        int finalJ = j;
                        labels[i][j].setOnMouseEntered((MouseEvent e) ->{
                            labels[finalI][finalJ].setScaleX(1.1);
                            labels[finalI][finalJ].setScaleY(1.1);
                        });
                        labels[i][j].setOnMouseExited((MouseEvent e) ->{
                            labels[finalI][finalJ].setScaleX(1);
                            labels[finalI][finalJ].setScaleY(1);
                        });
                        labels[i][j].setOnMouseClicked((MouseEvent e) ->{
                            int temp = data[finalI][finalJ];
                            int tempi, tempj;
                            if(setValueCount.get() > dimension*dimension){
                                setValueCount.set(1);
                            }
                            tempi = setValueCount.get() % dimension == 0 ? setValueCount.get() / dimension : setValueCount.get() / dimension + 1;
                            tempj = setValueCount.get() % dimension == 0 ? dimension : setValueCount.get() % dimension;

                            if(isfixed[tempi][tempj]){
                                tempi = 1;
                                tempj = 1;
                            }
                            while (isfixed[tempi][tempj]){
                                if(tempj == dimension){
                                    if(tempi != dimension){
                                        tempj = 1;
                                        tempi++;
                                    }
                                    else {
                                        tempi = 1;
                                        tempj = 1;
                                    }
                                }
                                else {
                                    tempj++;
                                }
                            }

                            int valueReturned = data[tempi][tempj];
                            data[tempi][tempj] = temp;
                            data[finalI][finalJ] = valueReturned;
                            labels[tempi][tempj].setText(""+data[tempi][tempj]);
                            labels[finalI][finalJ].setText(""+data[finalI][finalJ]);
                            labels[finalI][finalJ].setStyle("-fx-font-weight:bold;-fx-text-fill:olive");
                            isfixed[finalI][finalJ] = true;


                            //change left upper
                            if(tempi == tempj){
                                sumData[0] = sumData[0] + temp - valueReturned;
                                sumData[1] = sumData[0];
                                sumLables[0].setText(""+sumData[0]);
                                sumLables[1].setText(""+sumData[1]);
                            }
                            if(finalI == finalJ){
                                sumData[0] = sumData[0] - temp + valueReturned;
                                sumData[1] = sumData[0];
                                sumLables[0].setText(""+sumData[0]);
                                sumLables[1].setText(""+sumData[1]);
                            }

                            //change upper
                            sumData[tempj*2] = sumData[tempj*2] + temp - valueReturned;
                            sumData[tempj*2+1] = sumData[tempj*2];
                            sumLables[tempj*2].setText(""+sumData[tempj*2]);
                            sumLables[tempj*2+1].setText(""+sumData[tempj*2+1]);

                            sumData[finalJ*2] = sumData[finalJ*2] - temp + valueReturned;
                            sumData[finalJ*2+1] = sumData[finalJ*2];
                            sumLables[finalJ*2].setText(""+sumData[finalJ*2]);
                            sumLables[finalJ*2+1].setText(""+sumData[finalJ*2+1]);

                            //change right upper
                            if(tempi + tempj == dimension + 1){
                                sumData[2*dimension+2] = sumData[2*dimension+2] + temp - valueReturned;
                                sumData[2*dimension+3] = sumData[2*dimension+2];
                                sumLables[2*dimension+2].setText(""+sumData[2*dimension+2]);
                                sumLables[2*dimension+3].setText(""+sumData[2*dimension+3]);
                            }
                            if(finalI + finalJ == dimension + 1){
                                sumData[2*dimension+2] = sumData[2*dimension+2] - temp + valueReturned;
                                sumData[2*dimension+3] = sumData[2*dimension+2];
                                sumLables[2*dimension+2].setText(""+sumData[2*dimension+2]);
                                sumLables[2*dimension+3].setText(""+sumData[2*dimension+3]);
                            }

                            //change right
                            sumData[2*dimension+2+tempi*2] = sumData[2*dimension+2+tempi*2] + temp - valueReturned;
                            sumData[2*dimension+3+tempi*2] = sumData[2*dimension+2+tempi*2];
                            sumLables[2*dimension+2+tempi*2].setText(""+sumData[2*dimension+2+tempi*2]);
                            sumLables[2*dimension+3+tempi*2].setText(""+sumData[2*dimension+3+tempi*2]);

                            sumData[2*dimension+2+finalI*2] = sumData[2*dimension+2+finalI*2] - temp + valueReturned;
                            sumData[2*dimension+3+finalI*2] = sumData[2*dimension+2+finalI*2];
                            sumLables[2*dimension+2+finalI*2].setText(""+sumData[2*dimension+2+finalI*2]);
                            sumLables[2*dimension+3+finalI*2].setText(""+sumData[2*dimension+3+finalI*2]);

                            setValueCount.getAndIncrement();

                        });
                    }

                }
            }


        }
        else if(state == 1){
            state = 2;
            initialButton.setText("STOP");

            MSMain.msmain(hasConstrain,dimension,data,isfixed);

        }
        else if(state == 2){
            if(error == 0){
                state = 0;
                initialButton.setText("INITIAL");
            }
            else {
                state = 1;
                initialButton.setText("START");
            }
        }


    }

    @FXML
    void handleOpen() {
        FileChooser fileChooser = new FileChooser();
        // Set extension filter
        FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(
                "XML files (*.xml)", "*.xml");
        fileChooser.getExtensionFilters().add(extFilter);
        // Show save file dialog
        File file = fileChooser.showOpenDialog(Main.getPrimaryStage());
        if (file != null) {
            loadMatrixDataFromFile(file);
        }
    }

    @FXML
    void handleSaveAs() {
        FileChooser fileChooser = new FileChooser();
        FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(
                "XML files (*.xml)", "*.xml");
        fileChooser.getExtensionFilters().add(extFilter);
        File file = fileChooser.showSaveDialog(Main.getPrimaryStage());

        if (file != null) {
            // Make sure it has the correct extension
            if (!file.getPath().endsWith(".xml")) {
                file = new File(file.getPath() + ".xml");
            }
            savePersonDataToFile(file);
        }
    }

    public void loadMatrixDataFromFile(File file) {
        try {
            JAXBContext context = JAXBContext
                    .newInstance(WrapperMagicSquare.class);
            Unmarshaller um = context.createUnmarshaller();
            // Reading XML from the file and unmarshalling.
            WrapperMagicSquare wrapper = (WrapperMagicSquare) um.unmarshal(file);

            dimension = wrapper.getDimension();
            generation = wrapper.getGeneration();
            data = wrapper.getGrids();
            isfixed = wrapper.getIsFixed();

            hasConstrain = wrapper.getConstrain();
            isOpen = true;
            handleInitial();

            // Save the file path to the registry.
            Main.setPersonFilePath(file);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void savePersonDataToFile(File file) {
        try {
            JAXBContext context = JAXBContext
                    .newInstance(WrapperMagicSquare.class);
            Marshaller m = context.createMarshaller();
            m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

            // Wrapping our person data.
            WrapperMagicSquare wrapper = new WrapperMagicSquare();
            wrapper.setHasConstrain(hasConstrain);
            wrapper.setGeneration(generation);

            wrapper.setDimension(dimension);
            wrapper.setGrids(data);
            wrapper.setIsFixed(isfixed);

            // Marshalling and saving XML to the file.
            m.marshal(wrapper, file);
            // Save the file path to the registry.
            Main.setPersonFilePath(file);
        } catch (Exception e) { // catches ANY exception
            e.printStackTrace();
            System.out.println("save wrong");
        }
    }
}
